// Copyright Epic Games, Inc. All Rights Reserved.

#include <zencore/thread.h>

#include <zencore/except.h>
#include <zencore/filesystem.h>
#include <zencore/fmtutils.h>
#include <zencore/scopeguard.h>
#include <zencore/string.h>
#include <zencore/testing.h>
#include <zencore/trace.h>

#include <thread>

#if ZEN_PLATFORM_LINUX
#	if !defined(_GNU_SOURCE)
#		define _GNU_SOURCE	 // for semtimedop()
#	endif
#endif

#if !ZEN_USE_WINDOWS_EVENTS
#	include <chrono>
#	include <condition_variable>
#	include <mutex>
#endif

#if ZEN_PLATFORM_WINDOWS
#	include <zencore/windows.h>
#else
#	include <fcntl.h>
#	include <pthread.h>
#	include <signal.h>
#	include <sys/file.h>
#	include <sys/sem.h>
#	include <sys/stat.h>
#	include <sys/syscall.h>
#	include <sys/wait.h>
#	include <time.h>
#	include <unistd.h>
#endif

ZEN_THIRD_PARTY_INCLUDES_START
#include <fmt/format.h>
ZEN_THIRD_PARTY_INCLUDES_END

namespace zen {

#if ZEN_PLATFORM_WINDOWS
// The information on how to set the thread name comes from
// a MSDN article: http://msdn2.microsoft.com/en-us/library/xcb2z8hs.aspx
const DWORD kVCThreadNameException = 0x406D1388;
typedef struct tagTHREADNAME_INFO
{
	DWORD  dwType;		// Must be 0x1000.
	LPCSTR szName;		// Pointer to name (in user addr space).
	DWORD  dwThreadID;	// Thread ID (-1=caller thread).
	DWORD  dwFlags;		// Reserved for future use, must be zero.
} THREADNAME_INFO;
// The SetThreadDescription API was brought in version 1607 of Windows 10.
typedef HRESULT(WINAPI* SetThreadDescription)(HANDLE hThread, PCWSTR lpThreadDescription);
// This function has try handling, so it is separated out of its caller.
void
SetNameInternal(DWORD thread_id, const char* name)
{
	THREADNAME_INFO info;
	info.dwType		= 0x1000;
	info.szName		= name;
	info.dwThreadID = thread_id;
	info.dwFlags	= 0;
	__try
	{
		RaiseException(kVCThreadNameException, 0, sizeof(info) / sizeof(DWORD), reinterpret_cast<DWORD_PTR*>(&info));
	}
	__except (EXCEPTION_CONTINUE_EXECUTION)
	{
	}
}
#endif

void
SetCurrentThreadName([[maybe_unused]] std::string_view ThreadName)
{
	std::string ThreadNameZ{ThreadName};
	const int	ThreadId = GetCurrentThreadId();
#if ZEN_WITH_TRACE
	trace::ThreadRegister(ThreadNameZ.c_str(), /* system id */ ThreadId, /* sort id */ 0);
#endif	// ZEN_WITH_TRACE

#if ZEN_PLATFORM_WINDOWS
	// The SetThreadDescription API works even if no debugger is attached.
	static auto SetThreadDescriptionFunc =
		reinterpret_cast<SetThreadDescription>(::GetProcAddress(::GetModuleHandle(L"Kernel32.dll"), "SetThreadDescription"));

	if (SetThreadDescriptionFunc)
	{
		SetThreadDescriptionFunc(::GetCurrentThread(), Utf8ToWide(ThreadName).c_str());
	}

	// The debugger needs to be around to catch the name in the exception.  If
	// there isn't a debugger, we are just needlessly throwing an exception.
	if (::IsDebuggerPresent())
	{
		SetNameInternal(ThreadId, ThreadNameZ.c_str());
	}
#elif ZEN_PLATFORM_MAC
	pthread_setname_np(ThreadNameZ.c_str());
#else
	pthread_setname_np(pthread_self(), ThreadNameZ.c_str());
#endif
}  // namespace zen

void
RwLock::AcquireShared() noexcept
{
	m_Mutex.lock_shared();
}

void
RwLock::ReleaseShared() noexcept
{
	m_Mutex.unlock_shared();
}

void
RwLock::AcquireExclusive() noexcept
{
	m_Mutex.lock();
}

void
RwLock::ReleaseExclusive() noexcept
{
	m_Mutex.unlock();
}

//////////////////////////////////////////////////////////////////////////

#if !ZEN_USE_WINDOWS_EVENTS
struct EventInner
{
	std::mutex				Mutex;
	std::condition_variable CondVar;
	std::atomic_bool		bSet{false};
};
#endif	// !ZEN_PLATFORM_WINDOWS

Event::Event()
{
	bool bManualReset  = true;
	bool bInitialState = false;

#if ZEN_USE_WINDOWS_EVENTS
	m_EventHandle = CreateEvent(nullptr, bManualReset, bInitialState, nullptr);
#else
	ZEN_UNUSED(bManualReset);
	auto*			 Inner = new EventInner();
	std::unique_lock Lock(Inner->Mutex);
	Inner->bSet	  = bInitialState;
	m_EventHandle = Inner;
#endif
}

Event::~Event()
{
	Close();
}

void
Event::Set()
{
#if ZEN_USE_WINDOWS_EVENTS
	SetEvent(m_EventHandle);
#else
	std::atomic_thread_fence(std::memory_order_acquire);
	auto* Inner = (EventInner*)m_EventHandle;
	{
		std::unique_lock Lock(Inner->Mutex);
		Inner->bSet.store(true);
		Inner->CondVar.notify_all();
	}
#endif
}

void
Event::Reset()
{
#if ZEN_USE_WINDOWS_EVENTS
	ResetEvent(m_EventHandle);
#else
	std::atomic_thread_fence(std::memory_order_acquire);
	auto* Inner = (EventInner*)m_EventHandle;
	{
		std::unique_lock Lock(Inner->Mutex);
		Inner->bSet.store(false);
	}
#endif
}

void
Event::Close()
{
#if ZEN_USE_WINDOWS_EVENTS
	CloseHandle(m_EventHandle);
	m_EventHandle = nullptr;
#else
	std::atomic_thread_fence(std::memory_order_acquire);
	auto* Inner = (EventInner*)m_EventHandle;
	{
		std::unique_lock Lock(Inner->Mutex);
		Inner->bSet.store(true);
		m_EventHandle = nullptr;
	}
	delete Inner;
#endif
}

bool
Event::Wait(int TimeoutMs)
{
#if ZEN_USE_WINDOWS_EVENTS
	using namespace std::literals;

	const DWORD Timeout = (TimeoutMs < 0) ? INFINITE : TimeoutMs;

	DWORD Result = WaitForSingleObject(m_EventHandle, Timeout);

	if (Result == WAIT_FAILED)
	{
		zen::ThrowLastError("Event wait failed"sv);
	}

	return (Result == WAIT_OBJECT_0);
#else
	std::atomic_thread_fence(std::memory_order_acquire);
	auto* Inner = reinterpret_cast<EventInner*>(m_EventHandle);

	if (Inner->bSet.load())
	{
		return true;
	}

	if (TimeoutMs >= 0)
	{
		std::unique_lock Lock(Inner->Mutex);

		if (Inner->bSet.load())
		{
			return true;
		}

		return Inner->CondVar.wait_for(Lock, std::chrono::milliseconds(TimeoutMs), [&] { return Inner->bSet.load(); });
	}

	// Infinite wait. This does not actually call the wait() function to work around
	// an apparent issue in the underlying implementation.

	std::unique_lock Lock(Inner->Mutex);

	if (!Inner->bSet.load())
	{
		while (!Inner->CondVar.wait_for(Lock, std::chrono::milliseconds(1000), [&] { return Inner->bSet.load(); }))
			;
	}

	return true;
#endif
}

//////////////////////////////////////////////////////////////////////////

NamedEvent::NamedEvent(std::string_view EventName)
{
#if ZEN_PLATFORM_WINDOWS
	using namespace std::literals;

	ExtendableStringBuilder<64> Name;
	Name << "Local\\"sv;
	Name << EventName;

	m_EventHandle = CreateEventA(nullptr, true, false, Name.c_str());
#elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
	// Create a file to back the semaphore
	ExtendableStringBuilder<64> EventPath;
	EventPath << "/tmp/" << EventName;

	int Fd = open(EventPath.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0666);
	if (Fd < 0)
	{
		ThrowLastError(fmt::format("Failed to create '{}' for named event", EventPath));
	}
	fchmod(Fd, 0666);

	// Use the file path to generate an IPC key
	key_t IpcKey = ftok(EventPath.c_str(), 1);
	if (IpcKey < 0)
	{
		close(Fd);
		ThrowLastError("Failed to create an SysV IPC key");
	}

	// Use the key to create/open the semaphore
	int Sem = semget(IpcKey, 1, 0600 | IPC_CREAT);
	if (Sem < 0)
	{
		close(Fd);
		ThrowLastError("Failed creating an SysV semaphore");
	}

	// Atomically claim ownership of the semaphore's key. The owner initialises
	// the semaphore to 1 so we can use the wait-for-zero op as that does not
	// modify the semaphore's value on a successful wait.
	int LockResult = flock(Fd, LOCK_EX | LOCK_NB);
	if (LockResult == 0)
	{
		// This isn't thread safe really. Another thread could open the same
		// semaphore and successfully wait on it in the period of time where
		// this comment is but before the semaphore's initialised.
		semctl(Sem, 0, SETVAL, 1);
	}

	// Pack into the handle
	static_assert(sizeof(Sem) + sizeof(Fd) <= sizeof(void*), "Semaphore packing assumptions not met");
	intptr_t Packed;
	Packed = intptr_t(Sem) << 32;
	Packed |= intptr_t(Fd) & 0xffff'ffff;
	m_EventHandle = (void*)Packed;
#endif
	ZEN_ASSERT(m_EventHandle != nullptr);
}

NamedEvent::~NamedEvent()
{
	Close();
}

void
NamedEvent::Close()
{
	if (m_EventHandle == nullptr)
	{
		return;
	}

#if ZEN_PLATFORM_WINDOWS
	CloseHandle(m_EventHandle);
#elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
	int Fd		  = int(intptr_t(m_EventHandle) & 0xffff'ffff);

	if (flock(Fd, LOCK_EX | LOCK_NB) == 0)
	{
		std::filesystem::path Name = PathFromHandle((void*)(intptr_t(Fd)));
		unlink(Name.c_str());

		flock(Fd, LOCK_UN | LOCK_NB);
		close(Fd);

		int Sem = int(intptr_t(m_EventHandle) >> 32);
		semctl(Sem, 0, IPC_RMID);
	}
#endif

	m_EventHandle = nullptr;
}

void
NamedEvent::Set()
{
	ZEN_ASSERT(m_EventHandle != nullptr);
#if ZEN_PLATFORM_WINDOWS
	SetEvent(m_EventHandle);
#elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
	int Sem = int(intptr_t(m_EventHandle) >> 32);
	semctl(Sem, 0, SETVAL, 0);
#endif
}

bool
NamedEvent::Wait(int TimeoutMs)
{
	ZEN_ASSERT(m_EventHandle != nullptr);
#if ZEN_PLATFORM_WINDOWS
	const DWORD Timeout = (TimeoutMs < 0) ? INFINITE : TimeoutMs;

	DWORD Result = WaitForSingleObject(m_EventHandle, Timeout);

	if (Result == WAIT_FAILED)
	{
		using namespace std::literals;
		zen::ThrowLastError("Event wait failed"sv);
	}

	return (Result == WAIT_OBJECT_0);
#elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
	int Sem = int(intptr_t(m_EventHandle) >> 32);

	int			  Result;
	struct sembuf SemOp = {};

	if (TimeoutMs < 0)
	{
		Result = semop(Sem, &SemOp, 1);
		return Result == 0;
	}

#	if defined(_GNU_SOURCE)
	const int		TimeoutSec	 = TimeoutMs / 1000;
	struct timespec TimeoutValue = {
		.tv_sec	 = TimeoutSec,
		.tv_nsec = (TimeoutMs - (TimeoutSec * 1000)) * 1000000,
	};
	Result = semtimedop(Sem, &SemOp, 1, &TimeoutValue);
#	else
	const int SleepTimeMs = 10;
	SemOp.sem_flg		  = IPC_NOWAIT;
	do
	{
		Result = semop(Sem, &SemOp, 1);
		if (Result == 0 || errno != EAGAIN)
		{
			break;
		}

		Sleep(SleepTimeMs);
		TimeoutMs -= SleepTimeMs;
	} while (TimeoutMs > 0);
#	endif	// _GNU_SOURCE
	return Result == 0;
#endif
}

//////////////////////////////////////////////////////////////////////////

NamedMutex::~NamedMutex()
{
#if ZEN_PLATFORM_WINDOWS
	if (m_MutexHandle)
	{
		CloseHandle(m_MutexHandle);
	}
#elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
	int Inner = int(intptr_t(m_MutexHandle));
	flock(Inner, LOCK_UN);
	close(Inner);
#endif
}

bool
NamedMutex::Create(std::string_view MutexName)
{
#if ZEN_PLATFORM_WINDOWS
	ZEN_ASSERT(m_MutexHandle == nullptr);

	using namespace std::literals;

	ExtendableStringBuilder<64> Name;
	Name << "Global\\"sv;
	Name << MutexName;

	m_MutexHandle = CreateMutexA(nullptr, /* InitialOwner */ TRUE, Name.c_str());

	return !!m_MutexHandle;
#elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
	ExtendableStringBuilder<64> Name;
	Name << "/tmp/" << MutexName;

	int Inner = open(Name.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0666);
	if (Inner < 0)
	{
		return false;
	}
	fchmod(Inner, 0666);

	if (flock(Inner, LOCK_EX) != 0)
	{
		close(Inner);
		Inner = 0;
		return false;
	}

	m_MutexHandle = (void*)(intptr_t(Inner));
	return true;
#endif	// ZEN_PLATFORM_WINDOWS
}

bool
NamedMutex::Exists(std::string_view MutexName)
{
#if ZEN_PLATFORM_WINDOWS
	using namespace std::literals;

	ExtendableStringBuilder<64> Name;
	Name << "Global\\"sv;
	Name << MutexName;

	void* MutexHandle = OpenMutexA(SYNCHRONIZE, /* InheritHandle */ FALSE, Name.c_str());

	if (MutexHandle == nullptr)
	{
		return false;
	}

	CloseHandle(MutexHandle);

	return true;
#elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
	ExtendableStringBuilder<64> Name;
	Name << "/tmp/" << MutexName;

	bool bExists = false;
	int	 Fd		 = open(Name.c_str(), O_RDWR | O_CLOEXEC);
	if (Fd >= 0)
	{
		if (flock(Fd, LOCK_EX | LOCK_NB) == 0)
		{
			flock(Fd, LOCK_UN | LOCK_NB);
		}
		else
		{
			bExists = true;
		}
		close(Fd);
	}

	return bExists;
#endif	// ZEN_PLATFORM_WINDOWS
}

int
GetCurrentThreadId()
{
#if ZEN_PLATFORM_WINDOWS
	return ::GetCurrentThreadId();
#elif ZEN_PLATFORM_LINUX
	return int(syscall(SYS_gettid));
#elif ZEN_PLATFORM_MAC
	return int(pthread_mach_thread_np(pthread_self()));
#endif
}

void
Sleep(int ms)
{
#if ZEN_PLATFORM_WINDOWS
	::Sleep(ms);
#else
	usleep(ms * 1000U);
#endif
}

//////////////////////////////////////////////////////////////////////////
//
// Testing related code follows...
//

#if ZEN_WITH_TESTS

void
thread_forcelink()
{
}

TEST_SUITE_BEGIN("core.thread");

TEST_CASE("GetCurrentThreadId")
{
	CHECK_FALSE(GetCurrentThreadId() == 0);
}

TEST_CASE("NamedEvent")
{
	std::string Name = "zencore_test_event";
	NamedEvent	TestEvent(Name);

	// Timeout test
	for (uint32_t i = 0; i < 8; ++i)
	{
		bool bEventSet = TestEvent.Wait(10);
		CHECK(!bEventSet);
	}

	NamedEvent ReadyEvent(Name + "_ready");

	// Thread check
	std::thread Waiter = std::thread([Name]() {
		NamedEvent ReadyEvent(Name + "_ready");
		ReadyEvent.Set();

		NamedEvent TestEvent(Name);
		TestEvent.Wait(1000);
	});

	ReadyEvent.Wait();

	zen::Sleep(100);
	TestEvent.Set();

	Waiter.join();

	// Manual reset property
	for (uint32_t i = 0; i < 8; ++i)
	{
		bool bEventSet = TestEvent.Wait(100);
		CHECK(bEventSet);
	}
}

TEST_CASE("NamedMutex")
{
	static const char* Name = "zen_test_mutex";

	CHECK(!NamedMutex::Exists(Name));

	{
		NamedMutex TestMutex;
		CHECK(TestMutex.Create(Name));
		CHECK(NamedMutex::Exists(Name));
	}

	CHECK(!NamedMutex::Exists(Name));
}

TEST_SUITE_END();

#endif	// ZEN_WITH_TESTS

}  // namespace zen
